View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/betwixt/src/java/org/apache/commons/betwixt/io/BeanCreateRule.java,v 1.11 2002/08/29 21:40:29 rdonkin Exp $ 3 * $Revision: 1.11 $ 4 * $Date: 2002/08/29 21:40:29 $ 5 * 6 * ==================================================================== 7 * 8 * The Apache Software License, Version 1.1 9 * 10 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights 11 * reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in 22 * the documentation and/or other materials provided with the 23 * distribution. 24 * 25 * 3. The end-user documentation included with the redistribution, if 26 * any, must include the following acknowlegement: 27 * "This product includes software developed by the 28 * Apache Software Foundation (http://www.apache.org/)." 29 * Alternately, this acknowlegement may appear in the software itself, 30 * if and wherever such third-party acknowlegements normally appear. 31 * 32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 33 * Foundation" must not be used to endorse or promote products derived 34 * from this software without prior written permission. For written 35 * permission, please contact apache@apache.org. 36 * 37 * 5. Products derived from this software may not be called "Apache" 38 * nor may "Apache" appear in their names without prior written 39 * permission of the Apache Group. 40 * 41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 52 * SUCH DAMAGE. 53 * ==================================================================== 54 * 55 * This software consists of voluntary contributions made by many 56 * individuals on behalf of the Apache Software Foundation. For more 57 * information on the Apache Software Foundation, please see 58 * <http://www.apache.org/>;. 59 * 60 * $Id: BeanCreateRule.java,v 1.11 2002/08/29 21:40:29 rdonkin Exp $ 61 */ 62 package org.apache.commons.betwixt.io; 63 64 import java.util.HashMap; 65 import java.util.Iterator; 66 import java.util.List; 67 import java.util.Map; 68 69 import org.apache.commons.betwixt.AttributeDescriptor; 70 import org.apache.commons.betwixt.ElementDescriptor; 71 import org.apache.commons.betwixt.XMLBeanInfo; 72 import org.apache.commons.betwixt.XMLIntrospector; 73 import org.apache.commons.betwixt.expression.Context; 74 import org.apache.commons.betwixt.expression.MethodUpdater; 75 import org.apache.commons.betwixt.expression.Updater; 76 import org.apache.commons.betwixt.digester.XMLIntrospectorHelper; 77 78 import org.apache.commons.digester.Rule; 79 import org.apache.commons.digester.Rules; 80 import org.apache.commons.digester.Digester; 81 82 import org.apache.commons.logging.Log; 83 import org.apache.commons.logging.LogFactory; 84 85 import org.xml.sax.Attributes; 86 87 /*** <p><code>BeanCreateRule</code> is a Digester Rule for creating beans 88 * from the betwixt XML metadata.</p> 89 * 90 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 91 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a> 92 * @version $Revision: 1.11 $ 93 */ 94 public class BeanCreateRule extends Rule { 95 96 /*** Logger */ 97 private static final Log log = LogFactory.getLog( BeanCreateRule.class ); 98 99 /*** Set log to be used by <code>BeanCreateRule</code> instances */ 100 public static void setLog(Log log) { 101 log = log; 102 } 103 104 /*** The descriptor of this element */ 105 private ElementDescriptor descriptor; 106 /*** The Context used when evaluating Updaters */ 107 private Context context; 108 /*** Have we added our child rules to the digester? */ 109 private boolean addedChildren; 110 /*** In this begin-end loop did we actually create a new bean */ 111 private boolean createdBean; 112 /*** The type of the bean to create */ 113 private Class beanClass; 114 /*** The prefix added to digester rules */ 115 private String pathPrefix; 116 /*** Beans digested indexed by <code>ID</code> */ 117 private Map beansById = new HashMap(); 118 /*** Use id's to match beans? */ 119 private boolean matchIDs = true; 120 121 /*** 122 * Convenience constructor which uses <code>ID's</code> for matching. 123 */ 124 public BeanCreateRule( 125 ElementDescriptor descriptor, 126 Class beanClass, 127 String pathPrefix ) 128 { 129 this( descriptor, beanClass, pathPrefix, true ); 130 } 131 132 /*** 133 * Constructor taking a class. 134 */ 135 public BeanCreateRule( 136 ElementDescriptor descriptor, 137 Class beanClass, 138 String pathPrefix, 139 boolean matchIDs ) { 140 this( 141 descriptor, 142 beanClass, 143 new Context(), 144 pathPrefix, 145 matchIDs); 146 } 147 148 /*** 149 * Convenience constructor which uses <code>ID's</code> for matching. 150 */ 151 public BeanCreateRule( ElementDescriptor descriptor, Class beanClass ) { 152 this( descriptor, beanClass, true ); 153 } 154 155 /*** 156 * Constructor uses standard qualified name. 157 */ 158 public BeanCreateRule( ElementDescriptor descriptor, Class beanClass, boolean matchIDs ) { 159 this( descriptor, beanClass, descriptor.getQualifiedName() + "/" , matchIDs ); 160 } 161 162 /*** 163 * Convenience constructor which uses <code>ID's</code> for match. 164 */ 165 public BeanCreateRule( 166 ElementDescriptor descriptor, 167 Context context, 168 String pathPrefix ) { 169 this( descriptor, context, pathPrefix, true ); 170 } 171 172 /*** 173 * Constructor taking a context. 174 */ 175 public BeanCreateRule( 176 ElementDescriptor descriptor, 177 Context context, 178 String pathPrefix, 179 boolean matchIDs ) { 180 this( 181 descriptor, 182 descriptor.getSingularPropertyType(), 183 context, 184 pathPrefix, 185 matchIDs ); 186 } 187 188 /*** 189 * Base constructor (used by other constructors). 190 */ 191 private BeanCreateRule( 192 ElementDescriptor descriptor, 193 Class beanClass, 194 Context context, 195 String pathPrefix, 196 boolean matchIDs ) { 197 this.descriptor = descriptor; 198 this.context = context; 199 this.beanClass = beanClass; 200 this.pathPrefix = pathPrefix; 201 this.matchIDs = matchIDs; 202 if (log.isTraceEnabled()) { 203 log.trace("Created bean create rule"); 204 log.trace("Descriptor=" + descriptor); 205 log.trace("Class=" + beanClass); 206 log.trace("Path prefix=" + pathPrefix); 207 } 208 } 209 210 211 212 // Rule interface 213 //------------------------------------------------------------------------- 214 215 /*** 216 * Process the beginning of this element. 217 * 218 * @param attributes The attribute list of this element 219 */ 220 public void begin(Attributes attributes) throws Exception { 221 log.debug( "Called with descriptor: " + descriptor + " propertyType: " + descriptor.getPropertyType() ); 222 223 if (log.isTraceEnabled()) { 224 int attributesLength = attributes.getLength(); 225 if (attributesLength > 0) { 226 log.trace("Attributes:"); 227 } 228 for (int i=0, size=attributesLength; i<size; i++) { 229 log.trace("Local:" + attributes.getLocalName(i)); 230 log.trace("URI:" + attributes.getURI(i)); 231 log.trace("QName:" + attributes.getQName(i)); 232 } 233 } 234 235 236 237 // XXX: if a single rule instance gets reused and nesting occurs 238 // XXX: we should probably use a stack of booleans to test if we created a bean 239 // XXX: or let digester take nulls, which would be easier for us ;-) 240 createdBean = false; 241 242 Object instance = null; 243 if ( beanClass != null ) { 244 instance = createBean(attributes); 245 if ( instance != null ) { 246 createdBean = true; 247 248 context.setBean( instance ); 249 digester.push(instance); 250 251 252 // if we are a reference to a type we should lookup the original 253 // as this ElementDescriptor will be 'hollow' and have no child attributes/elements. 254 // XXX: this should probably be done by the NodeDescriptors... 255 ElementDescriptor typeDescriptor = getElementDescriptor( descriptor ); 256 //ElementDescriptor typeDescriptor = descriptor; 257 258 // iterate through all attributes 259 AttributeDescriptor[] attributeDescriptors = typeDescriptor.getAttributeDescriptors(); 260 if ( attributeDescriptors != null ) { 261 for ( int i = 0, size = attributeDescriptors.length; i < size; i++ ) { 262 AttributeDescriptor attributeDescriptor = attributeDescriptors[i]; 263 264 // The following isn't really the right way to find the attribute 265 // but it's quite robust. 266 // The idea is that you try both namespace and local name first 267 // and if this returns null try the qName. 268 String value = attributes.getValue( 269 attributeDescriptor.getURI(), 270 attributeDescriptor.getLocalName() 271 ); 272 273 if (value == null) { 274 value = attributes.getValue(attributeDescriptor.getQualifiedName()); 275 } 276 277 if (log.isTraceEnabled()) { 278 log.trace("Attr URL:" + attributeDescriptor.getURI()); 279 log.trace("Attr LocalName:" + attributeDescriptor.getLocalName() ); 280 log.trace(value); 281 } 282 283 Updater updater = attributeDescriptor.getUpdater(); 284 log.trace(updater); 285 if ( updater != null && value != null ) { 286 updater.update( context, value ); 287 } 288 } 289 } 290 291 addChildRules(); 292 293 // add bean for ID matching 294 if ( matchIDs ) { 295 // XXX need to support custom ID attribute names 296 // XXX i have a feeling that the current mechanism might need to change 297 // XXX so i'm leaving this till later 298 String id = attributes.getValue( "id" ); 299 if ( id != null ) { 300 beansById.put( id, instance ); 301 } 302 } 303 } 304 } 305 } 306 307 /*** 308 * Process the end of this element. 309 */ 310 public void end() throws Exception { 311 if ( createdBean ) { 312 313 // force any setters of the parent bean to be called for this new bean instance 314 Updater updater = descriptor.getUpdater(); 315 Object instance = context.getBean(); 316 317 Object top = digester.pop(); 318 if (digester.getCount() == 0) { 319 context.setBean(null); 320 }else{ 321 context.setBean( digester.peek() ); 322 } 323 324 if ( updater != null ) { 325 if ( log.isDebugEnabled() ) { 326 log.debug( "Calling updater for: " + descriptor + " with: " + instance + " on bean: " + context.getBean() ); 327 } 328 updater.update( context, instance ); 329 } 330 331 } 332 } 333 334 /*** 335 * Tidy up. 336 */ 337 public void finish() { 338 // clear beans map 339 beansById.clear(); 340 } 341 342 343 // Implementation methods 344 //------------------------------------------------------------------------- 345 346 /*** Factory method to create new bean instances */ 347 protected Object createBean(Attributes attributes) throws Exception { 348 // 349 // See if we've got an IDREF 350 // 351 // XXX This should be customizable but i'm not really convinced by the existing system 352 // XXX maybe it's going to have to change so i'll use 'idref' for nows 353 // 354 if ( matchIDs ) { 355 String idref = attributes.getValue( "idref" ); 356 if ( idref != null ) { 357 // XXX need to check up about ordering 358 // XXX this is a very simple system that assumes that id occurs before idrefs 359 // XXX would need some thought about how to implement a fuller system 360 Object bean = beansById.get( idref ); 361 if ( bean != null ) { 362 return bean; 363 } 364 } 365 } 366 367 try { 368 return beanClass.newInstance(); 369 } 370 catch (Exception e) { 371 log.warn( "Could not create instance of type: " + beanClass.getName() ); 372 return null; 373 } 374 } 375 376 /*** Adds the rules to the digester for all child elements */ 377 protected void addChildRules() { 378 if ( ! addedChildren ) { 379 addedChildren = true; 380 381 addChildRules( pathPrefix, descriptor ); 382 } 383 } 384 385 /*** Add child rules for given descriptor at given prefix */ 386 protected void addChildRules(String prefix, ElementDescriptor currentDescriptor ) { 387 BeanReader digester = getBeanReader(); 388 389 if (log.isTraceEnabled()) { 390 log.trace("Adding child rules for " + currentDescriptor + "@" + prefix); 391 } 392 393 // if we are a reference to a type we should lookup the original 394 // as this ElementDescriptor will be 'hollow' and have no child attributes/elements. 395 // XXX: this should probably be done by the NodeDescriptors... 396 ElementDescriptor typeDescriptor = getElementDescriptor( currentDescriptor ); 397 //ElementDescriptor typeDescriptor = descriptor; 398 399 400 ElementDescriptor[] childDescriptors = typeDescriptor.getElementDescriptors(); 401 if ( childDescriptors != null ) { 402 for ( int i = 0, size = childDescriptors.length; i < size; i++ ) { 403 final ElementDescriptor childDescriptor = childDescriptors[i]; 404 if (log.isTraceEnabled()) { 405 log.trace("Processing child " + childDescriptor); 406 } 407 408 String propertyName = childDescriptor.getPropertyName(); 409 String qualifiedName = childDescriptor.getQualifiedName(); 410 if ( qualifiedName == null ) { 411 log.trace("Ignoring"); 412 continue; 413 } 414 String path = prefix + qualifiedName; 415 // this code is for making sure that recursive elements 416 // can also be used.. 417 if (qualifiedName.equals(currentDescriptor.getQualifiedName())) { 418 log.trace("Creating generic rule for recursive elements"); 419 int index = -1; 420 if (childDescriptor.isWrapCollectionsInElement()) { 421 index = prefix.indexOf(qualifiedName); 422 if (index == -1) { 423 // shouldn't happen.. 424 continue; 425 } 426 int removeSlash = prefix.endsWith("/")?1:0; 427 path = "*/" + prefix.substring(index, prefix.length()-removeSlash); 428 }else{ 429 // we have a element/element type of thing.. 430 ElementDescriptor[] desc = currentDescriptor.getElementDescriptors(); 431 if (desc.length == 1) { 432 path = "*/"+desc[0].getQualifiedName(); 433 } 434 } 435 Rule rule = new BeanCreateRule( childDescriptor, context, path, matchIDs); 436 addRule(path, rule); 437 continue; 438 } 439 if ( childDescriptor.getUpdater() != null ) { 440 if (log.isTraceEnabled()) { 441 log.trace("Element has updater " 442 +((MethodUpdater) childDescriptor.getUpdater()).getMethod().getName()); 443 } 444 if ( childDescriptor.isPrimitiveType() ) { 445 addPrimitiveTypeRule(path, childDescriptor); 446 } 447 else { 448 // add the first child to the path 449 ElementDescriptor[] grandChildren = childDescriptor.getElementDescriptors(); 450 if ( grandChildren != null && grandChildren.length > 0 ) { 451 ElementDescriptor grandChild = grandChildren[0]; 452 String grandChildQName = grandChild.getQualifiedName(); 453 if ( grandChildQName != null && grandChildQName.length() > 0 ) { 454 if (childDescriptor.isWrapCollectionsInElement()) { 455 path += '/' + grandChildQName; 456 } 457 else{ 458 path = prefix + grandChildQName; 459 } 460 } 461 } 462 463 // maybe we are adding a primitve type to a collection/array 464 Class beanClass = childDescriptor.getSingularPropertyType(); 465 if ( XMLIntrospectorHelper.isPrimitiveType( beanClass ) ) { 466 addPrimitiveTypeRule(path, childDescriptor); 467 } 468 else { 469 Rule rule = new BeanCreateRule( childDescriptor, context, path + '/', matchIDs ); 470 addRule( path, rule ); 471 } 472 } 473 } else { 474 log.trace("Element does not have updater"); 475 } 476 477 ElementDescriptor[] grandChildren = childDescriptor.getElementDescriptors(); 478 if ( grandChildren != null && grandChildren.length > 0 ) { 479 log.trace("Adding grand children"); 480 addChildRules( path + '/', childDescriptor ); 481 } 482 } 483 } 484 } 485 486 /*** 487 * Get the associated bean reader. 488 */ 489 protected BeanReader getBeanReader() { 490 // XXX this breaks the rule contact 491 // XXX maybe the reader should be passed in the constructor 492 return (BeanReader) getDigester(); 493 } 494 495 /*** Allows the navigation from a reference to a property object to the descriptor defining what 496 * the property is. i.e. doing the join from a reference to a type to lookup its descriptor. 497 * This could be done automatically by the NodeDescriptors. Refer to TODO.txt for more info. 498 */ 499 protected ElementDescriptor getElementDescriptor( ElementDescriptor propertyDescriptor ) { 500 Class beanClass = propertyDescriptor.getSingularPropertyType(); 501 if ( beanClass != null ) { 502 XMLIntrospector introspector = getBeanReader().getXMLIntrospector(); 503 try { 504 XMLBeanInfo xmlInfo = introspector.introspect( beanClass ); 505 return xmlInfo.getElementDescriptor(); 506 } 507 catch (Exception e) { 508 log.warn( "Could not introspect class: " + beanClass, e ); 509 } 510 } 511 // could not find a better descriptor so use the one we've got 512 return propertyDescriptor; 513 } 514 515 /*** 516 * Adds a new Digester rule to process the text as a primitive type 517 */ 518 protected void addPrimitiveTypeRule(String path, final ElementDescriptor childDescriptor) { 519 Rule rule = new Rule() { 520 public void body(String text) throws Exception { 521 childDescriptor.getUpdater().update( context, text ); 522 } 523 }; 524 addRule( path, rule ); 525 } 526 527 /*** 528 * Safely add a rule with given path. 529 */ 530 protected void addRule(String path, Rule rule) { 531 Rules rules = digester.getRules(); 532 List matches = rules.match(null, path); 533 if ( matches.isEmpty() ) { 534 if ( log.isDebugEnabled() ) { 535 log.debug( "Adding digester rule for path: " + path + " rule: " + rule ); 536 } 537 digester.addRule( path, rule ); 538 } 539 else { 540 if ( log.isDebugEnabled() ) { 541 log.debug( "Ignoring duplicate digester rule for path: " + path + " rule: " + rule ); 542 } 543 } 544 } 545 546 /*** 547 * Return something meaningful for logging. 548 */ 549 public String toString() { 550 return "BeanCreateRule [path prefix=" + pathPrefix + " descriptor=" + descriptor + "]"; 551 } 552 553 }

This page was automatically generated by Maven